from kivy.app import App
from kivy.logger import Logger

from dryfire.model.moaModel import MOAModel
from dryfire.model.moasModel import HorizontalBarsMOAModel,\
    VerticalBarsMOAModel
from dryfire.model.targetsModel import\
    PaperPlateDryFireTargetModel, BeerCanDryFireTargetModel,\
    HeadDryFireTargetModel, HipsAndHeadDryFireTargetModel,\
    TorsoDryFireTargetModel
from dryfire.view.targetSlide import TargetSlide
from dryfire.model.persistentData import RangesModel

from math import floor


class RangesViewModel(object):
    """
    The view model for the InitialView class.
    """

    min_seconds_up = 4
    max_seconds_up = 10
    seconds_up_step = 2
    min_seconds_down = 5
    max_seconds_down = 20
    seconds_down_step = 5
    min_distance_feet = 1
    options_yards = (25, 50, 75, 100)

    def __init__(self, view):
        self._view = view
        self._appdata = App.get_running_app().appdata
        self._targets = {}
        self._build_targets()
        self._slides = {}
        self._build_slides()
        self._record_ident = None
        self._record = {}
        self._is_target = True

    # view methods

    def v_initialize_view(self):
        # initialize view with ranges records
        ranges = self._appdata['ranges']
        idents = ranges.data.keys()
        for i in idents:
            record = ranges.get(i)
            self._view.vm_add_range(i, record['description']) 
        
    # step 1 : the user pressed the add a new record button
    
    def w_handle_create_button_on_press(self, instance):
        self._record_ident = None
        self._record = self._appdata['ranges'].default_record
        self._set_record_to_mins()
        self.w_select_a_target()
        
    # other step 1 : the user selects a range record action
    #  to practice, edit or delete
    
    def w_handle_ranges_record(self, ident):
        # the user has selected a range record
        self._record_ident = ident
        self._record = self._appdata['ranges'].get(ident)
        self._set_record_to_mins()
        self.select_a_range_record_option()

    def select_a_range_record_option(self):
        from dryfire.view.rangesView import RangesView
        # offer options for the record
        options = RangesView.commands
        self._view.select_options('Options',
                                  options,
                                  options[0],
                                  self.w_handle_ranges_record_command,
                                  help='What would you like to do with this record?')

    def w_handle_ranges_record_command(self, command):
        from dryfire.view.rangesView import RangesView
        if command == RangesView.practice_command:
            # open the practice panel
            view = self._view
            # setup the target
            target_name = self._record['target']
            target = self._targets[target_name]['practice_target']
            if not isinstance(target, MOAModel):
                # this is a normal target
                self._appdata['target_vm'].uptime_seconds = self._record['up']
                bg = self._record['bg']
                yards = self._record['yards'] 
            else:
                # this is the moa bars
                self._appdata['target_vm'].uptime_seconds = 0
                bg = [0.5, 0.5, 0.5, 1.0]
                yards = 100
            self._appdata['target_vm'].set_fg_bg(self._record['fg'], bg)
            # set the target's pixels per inch
            cal_ident = self._record['calibration']
            calibrations = self._appdata['calibrations']
            cal_record = calibrations.get(cal_ident)
            target.pixels_per_inch = cal_record['pixels'] 
            target.position_graphics(indoor_distance_feet=self._record['feet'],
                                     outdoor_distance_yards=yards,
                                     view_size=self._view.size)
            self._appdata['target_vm'].target_model = target
            # setup the down times
            self._appdata['target_vm'].downtime_seconds = self._record['down']
            # display the panel
            self._appdata['app_view'].show_target()
        elif command == RangesView.edit_command:
            self.w_select_a_target()
        elif command == RangesView.delete_command:
            # let the user confirm deletion
            question = 'Are you sure that you want to '\
                       'delete the range record %s?' % (self._record['description'])
            self._view.question_yes_no('Delete a Range Record.',
                                       question,
                                       self.delete_a_target,
                                       back_handler=self.select_a_range_record_option)
        else:
            self._view.warn("Unknown command.")

    # step 2 : user deletes a target            

    def delete_a_target(self, true_false):
        if true_false is True:
            ident = self._record_ident
            backup = self._appdata['ranges'].backup() 
            ok = self._appdata['ranges'].remove(ident)
            if ok:
                ok, mess = self._appdata['ranges'].write()
                if ok:
                    self._view.vm_show_range_deleted(ident)
            if not ok:
                # unable to write the file
                self._appdata['ranges'].restore(backup)
                self._view.message_warn('Unable to delete %s.' % (self._record['description']))
        else:
            self._view.message_warn('Nothing deleted.')
                
    # step 2 : user edits a record
    # select a target

    def w_select_a_target(self):
        kwargs = {}
        if self._record_ident is not None:
            kwargs['back_handler'] = self.select_a_range_record_option
        # let the user select a target
        # self._record and self._record_ident are set
        names = self._slides.keys()
        names.sort()
        slides = []
        fts = []
        for i in names:
            slide = self._slides[i]
            ft = slide.vm_get_max_feet_for_view_size(self._view.message_carousel_size)
            fts.append(ft)
            slides.append(slide)
        min_ft = min(fts)
        for slide in slides:
            slide.vm_set_indoor_distance_feet(min_ft)
        default_name = self._record['target'] or names[0]
        self._view.select_slides('Select a Target.',
                                 slides,
                                 self._slides[default_name],
                                 self.w_handle_on_target_selected,
                                 **kwargs)

    def w_handle_on_target_selected(self, slide):
        # the user has selected a target slide from the slides
        self._record['target'] = slide.target.name
        self._is_target = not isinstance(slide.target, MOAModel)
        if self._is_target:
            # next is distance yards
            self.w_select_distance_yards()
        else:
            # the moa board is always at 100 yards.
            # next is distance feet
            self._record['yards'] = 100
            self.w_select_distance_feet()
                                 
    # step 3 : user selects yards

    def w_select_distance_yards(self):
        if len(self._record['target']) > 0:
            target_name = self._record['target']
            if self._record['yards'] < RangesViewModel.options_yards[0]:
                self._record['yards'] = RangesViewModel.options_yards[0]
            elif self._record['yards'] > RangesViewModel.options_yards[-1]:
                self._record['yards'] = RangesViewModel.options_yards[-1]
            default = str(self._record['yards'])
            help = 'If you were at a range shooting at this target, how far away would this target be from you?'
            self._view.select_options('The target distance in yards.',
                                      [str(i) for i in RangesViewModel.options_yards],
                                      default,
                                      self.w_handle_on_distance_yards_select,
                                      help=help,
                                      back_handler=self.w_select_a_target)

    def w_handle_on_distance_yards_select(self, text):
        """Store the yards in the record.
        Recalc the maximum feet value for this record based on yards.
        """
        yards = int(text)
        if self._record['yards'] != yards:
            self._record['yards'] = yards
        # next is select distance feet
        self.w_select_distance_feet()

    # step 4 : user distance feet widget methods
    
    def w_select_distance_feet(self):
        # recalc max feet
        v_size = self._view.size
        name = self._record['target']
        calc_target = self._targets[name]['calc_target'] 
        calc_target.position_graphics(outdoor_distance_yards=self._record['yards'])
        w, h = calc_target.sizer
        max_w_feet = floor(v_size[0] / w)
        max_h_feet = floor(v_size[1] / h)
        max_feet = int(min(max_w_feet, max_h_feet))
        self._targets[name]['max_feet'] = max_feet
        if self._record['feet'] > max_feet:
            self._record['feet'] = max_feet
        default = str(self._record['feet'])
        last = max_feet + 1
        options = [str(i) for i in range(RangesViewModel.min_distance_feet, last)]
        help = 'Select how far your eye will be from the screen when you are in your shooting stance.'
        if self._is_target:
            back_handler = self.w_select_distance_yards
        else:
            back_handler = self.w_select_a_target  
        self._view.select_options('Your eye\'s distance from this computer screen in feet. %i feet maximum.' % (max_feet),
                                  options,
                                  default,
                                  self.w_handle_on_distance_feet_select,
                                  help=help,
                                  back_handler=back_handler)

    def w_handle_on_distance_feet_select(self, text):
        self._record['feet'] = int(text)
        if self._is_target:
            # next is seconds up
            self.w_select_seconds_up()
        else:
            # next is calibration
            self.w_select_calibration()

    # step 5 : seconds up widget methods

    def w_select_seconds_up(self):
        # instance is the button
        if self._record['up'] < RangesViewModel.min_seconds_up:
            self._record['up'] = RangesViewModel.min_seconds_up
        elif self._record['up'] > RangesViewModel.max_seconds_up:
            self._record['up'] = RangesViewModel.max_seconds_up
        default = str(self._record['up'])
        options = [str(i) for i in range(RangesViewModel.min_seconds_up,
                                         RangesViewModel.max_seconds_up+1,
                                         RangesViewModel.seconds_up_step)]
        help = 'This is the amount of time that you need to'\
               '\n * bring your weapon up,'\
               '\n * lineup your sights,'\
               '\n * bring your sights on the target'\
               ' as you squeeze the trigger.'
        self._view.select_options('Target pop up time in seconds.',
                                  options,
                                  default,
                                  self.w_handle_on_seconds_up_select,
                                  help=help,
                                  back_handler=self.w_select_distance_feet)

    def w_handle_on_seconds_up_select(self, text):
        self._record['up'] = int(text)
        # next seconds down
        self.w_select_seconds_down()

    # step 6 : seconds down widget methods

    def w_select_seconds_down(self):
        if self._record['down'] < RangesViewModel.min_seconds_down:
            self._record['down'] = RangesViewModel.min_seconds_down
        elif self._record['down'] > RangesViewModel.max_seconds_down:
            self._record['down'] = RangesViewModel.max_seconds_down
        default = str(self._record['down'])
        help = 'You will probably want enough time to'\
               '\n * review the last shot,'\
               '\n * reload the snap cap,'\
               '\n * return to low ready and wait for the target to reappear.'
        options = [str(i) for i in range(RangesViewModel.min_seconds_down,
                                         RangesViewModel.max_seconds_down+1,
                                         RangesViewModel.seconds_down_step)]
        self._view.select_options('Target down time in seconds.',
                                  options,
                                  default,
                                  self.w_handle_on_seconds_down_select,
                                  help=help,
                                  back_handler=self.w_select_seconds_up)

    def w_handle_on_seconds_down_select(self, text):
        self._record['down'] = int(text)
        self.w_select_calibration()
        
    def w_select_calibration(self):
        calibrations = self._appdata['calibrations']
        options = calibrations.get_descriptions()
        r = calibrations.get(self._record['calibration'])
        if r is not None:
            default = r['description']
        else:
            default = options[0]
        if self._is_target:
            back_handler = self.w_select_seconds_down
        else:
            back_handler = self.w_select_distance_feet
        self._view.select_options('Calibration.',
                                  options,
                                  default,
                                  self.w_handle_on_calibration_select,
                                  back_handler=back_handler)
        
    def w_handle_on_calibration_select(self, description):
        calibrations = self._appdata['calibrations']
        ident = calibrations.get_ident_using_description(description)
        self._record['calibration'] = ident
        self.w_enter_description()

    # step 8 : describe the target and range
    
    def w_enter_description(self, **kwargs):
        kwargs['back_handler'] = self.w_select_seconds_down
        self._view.edit_line('Short description.',
                             'Enter a short description.',
                             self._record['description'],
                             self.w_handle_on_description_enter,
                             **kwargs)

    def w_handle_on_description_enter(self, description):
        description = description.strip()
        if len(description) > 0:
            ranges = self._appdata['ranges']
            ident = ranges.get_ident_using_description(description)
            if ident is not None and self._record_ident != ident:
                help = 'You are already using the description "%s"'\
                       ' with another target setup.' % (description) 
                self.w_enter_description(help=help)
            else:
                self._record['description'] = description
                self._save_record()
        else:
            help = 'You did not entered any text.'
            self.w_enter_description(help=help)

    # non public methods
    
    # non public slides methods
        
    def _build_slides(self):
        t = BeerCanDryFireTargetModel()
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = PaperPlateDryFireTargetModel()
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = HeadDryFireTargetModel()
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = HipsAndHeadDryFireTargetModel() 
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = TorsoDryFireTargetModel() 
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = HorizontalBarsMOAModel() 
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = VerticalBarsMOAModel() 
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
    
    # non public target methods
    
    def _target_dict(self, label, carousel_target, practice_target, calc_target, max_feet=-1):
        return {'label':label,
                'carousel_target':carousel_target,
                'practice_target':practice_target,
                'calc_target':calc_target,
                'max_feet':max_feet}

    def _build_targets(self):
        # beer can target
        bc_target = BeerCanDryFireTargetModel()
        bc_label = self._view.build_form_label(bc_target.name,
                                               bc_target.description)
        self._targets[bc_target.name] = self._target_dict(bc_label,
                                                          bc_target,
                                                          BeerCanDryFireTargetModel(),
                                                          BeerCanDryFireTargetModel())
        # paper plate target
        pp_target = PaperPlateDryFireTargetModel()
        pp_label = self._view.build_form_label(pp_target.name,
                                               pp_target.description)
        self._targets[pp_target.name] = self._target_dict(pp_label,
                                                          pp_target,
                                                          PaperPlateDryFireTargetModel(),
                                                          PaperPlateDryFireTargetModel())
        # head target
        h_target = HeadDryFireTargetModel()
        h_label = self._view.build_form_label(h_target.name,
                                              h_target.description)
        self._targets[h_target.name] = self._target_dict(h_label,
                                                         h_target,
                                                         HeadDryFireTargetModel(),
                                                         HeadDryFireTargetModel())
        # hips and head target
        hh_target = HipsAndHeadDryFireTargetModel()
        hh_label = self._view.build_form_label(hh_target.name,
                                               hh_target.description)
        self._targets[hh_target.name] = self._target_dict(hh_label,
                                                          hh_target,
                                                          HipsAndHeadDryFireTargetModel(),
                                                          HipsAndHeadDryFireTargetModel())
        # torso target
        t_target = TorsoDryFireTargetModel()
        t_label = self._view.build_form_label(t_target.name,
                                              t_target.description)
        self._targets[t_target.name] = self._target_dict(t_label,
                                                         t_target,
                                                         TorsoDryFireTargetModel(),
                                                         TorsoDryFireTargetModel())
        # horizontal bar moa board
        hbmoa_target = HorizontalBarsMOAModel()
        hbmoa_label = self._view.build_form_label(hbmoa_target.name,
                                                  hbmoa_target.description)
        self._targets[hbmoa_target.name] = self._target_dict(hbmoa_label,
                                                             hbmoa_target,
                                                             HorizontalBarsMOAModel(),
                                                             HorizontalBarsMOAModel())
        # vertical bar moa board
        vbmoa_target = VerticalBarsMOAModel()
        vbmoa_label = self._view.build_form_label(vbmoa_target.name,
                                                  vbmoa_target.description)
        self._targets[vbmoa_target.name] = self._target_dict(vbmoa_label,
                                                             vbmoa_target,
                                                             VerticalBarsMOAModel(),
                                                             VerticalBarsMOAModel())
    
    def _set_record_to_mins(self):
        min_yards = RangesViewModel.options_yards[0]
        if self._record['yards'] < min_yards:
            self._record['yards'] = min_yards
        if self._record['feet'] < RangesViewModel.min_distance_feet:
            self._record['feet'] = RangesViewModel.min_distance_feet
        if self._record['up'] < RangesViewModel.min_seconds_up:
            self._record['up'] = RangesViewModel.min_seconds_up
        if self._record['down'] < RangesViewModel.min_seconds_down:
            self._record['down'] = RangesViewModel.min_seconds_down

    def _save_record(self):
        ranges = self._appdata['ranges']
        # fix the record if its not for a target
        if self._is_target is False:
            self._record['yards'] = 100
        # save the record
        backup = ranges.backup()
        if self._record_ident is None:
            ok, ident = ranges.add(self._record_ident, self._record)
            if ok:
                ok, mess = ranges.write()
            if ok:
                self._view.vm_show_range_added(ident, self._record['description'])
        else:
            ok = ranges.set(self._record_ident, self._record)
            if ok:
                ok, mess = ranges.write()
            if ok:
                self._view.vm_show_range_edited(self._record_ident, self._record['description'])
